package cn.song.util;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static cn.hutool.json.JSONUtil.toJsonStr;

/**
 * Redis 工具类
 *
 * @author SongRenShuo
 * @date 2024/01/09
 */
@Slf4j
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 写入对象到 Redis，支持设置过期时间或永不过期
     *
     * @param key        键
     * @param value      值
     * @param expireTime 过期时间（单位：秒），-1 代表永不过期
     * @return boolean 操作是否成功
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public boolean setObject(final String key, Object value, Integer expireTime) {
        try {
            redisTemplate.opsForValue().set(key, value);
            isExpire(key, expireTime);
            return true;
        } catch (Exception e) {
            log.error("RedisUtil.setObject key:{}, value:{}, expireTime:{}", key, value, expireTime, e);
            return false;
        }
    }

    /**
     * 获取对象
     *
     * @param key 键
     * @return {@link Object}
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public Object getObject(final String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 获取对象
     *
     * @param key   键
     * @param clazz 对象类型
     * @return {@link T}
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public <T> T getObject(final String key, Class<T> clazz) {
        if (key == null) {
            return null;
        }
        // 使用泛型方法正确转换并返回对象
        Object obj = redisTemplate.opsForValue().get(key);
        return clazz.isInstance(obj) ? clazz.cast(obj) : null;
    }

    /**
     * 获取 String - 如果为 null 返回 ''
     *
     * @param key 键
     * @return {@link String}
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public String getString(final String key) {
        Object obj = redisTemplate.opsForValue().get(key);
        return obj == null ? "" : obj.toString();
    }

    /**
     * 获取集合
     *
     * @param key 键
     * @return {@link List }
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public List getList(final String key) {
        try {
            return redisTemplate.opsForList().range(key, 0, -1);
        } catch (Exception e) {
            log.error("RedisUtil.getList key:{}", key, e);
            return null;
        }
    }

    /**
     * 获取集合
     *
     * @param key         键
     * @param elementType 元素类型
     * @return {@link List}<{@link T}>
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public <T> List<T> getList(final String key, Class<T> elementType) {
        try {
            List<Object> list = redisTemplate.opsForList().range(key, 0, -1);
            if (list == null || list.isEmpty()) {
                return Collections.emptyList();
            }
            return JSONUtil.parseArray(list).toList(elementType);
        } catch (Exception e) {
            log.error("RedisUtil.getList key:{}", key, e);
            return null;
        }
    }

    /**
     * 写入集合
     *
     * @param key        键
     * @param value      值
     * @param expireTime 过期时间（单位：秒），-1 代表永不过期
     * @return boolean
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public boolean setList(final String key, Object value, Integer expireTime) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            isExpire(key, expireTime);
            return true;
        } catch (Exception e) {
            log.error("RedisUtil.setList key:{}, value:{}, expireTime:{}", key, value, expireTime, e);
            return false;
        }
    }

    /**
     * 通过模式匹配获取 Redis 中键的值。
     *
     * @param pattern 匹配键的模式，例如"name*"将匹配所有以"name_"开头的键
     * @return 包含匹配键的值的集合
     * @date 2024/09/03
     * @author SongRenShuo
     */
    public Set<String> scanValuesByPattern(final String pattern) {
        // 确保使用StringRedisSerializer来序列化key
        if (!(redisTemplate.getKeySerializer() instanceof StringRedisSerializer)) {
            throw new IllegalStateException("RedisTemplate must use StringRedisSerializer for keys.");
        }
        // 设置扫描的选项 开始扫描
        try (Cursor<byte[]> cursor = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection().scan(ScanOptions.scanOptions().match(pattern + "*").count(100).build())) {
            Set<String> results = new HashSet<>();
            while (cursor.hasNext()) {
                byte[] keyBytes = cursor.next();
                // 转换字节为字符串
                String key = new String(keyBytes);
                // 获取键对应的值
                Object value = redisTemplate.opsForValue().get(key);
                if (value != null) {
                    results.add(value.toString());
                }
            }
            return results;
        } catch (Exception e) {
            log.error("RedisUtil.scanValuesByPattern pattern:{}", pattern, e);
            return Collections.emptySet();
        }
    }

    /**
     * 通过模式匹配获取 Redis 中的键名集合
     *
     * @param pattern 匹配键的模式，例如"EXPORT_TASKLIST_*"将匹配所有以"EXPORT_TASKLIST_"开头的键
     * @return 包含匹配键名的集合
     * @date 2025/08/29
     * @author SongRenShuo
     */
    public Set<String> keys(final String pattern) {
        try {
            // 确保使用StringRedisSerializer来序列化key
            if (!(redisTemplate.getKeySerializer() instanceof StringRedisSerializer)) {
                throw new IllegalStateException("RedisTemplate must use StringRedisSerializer for keys.");
            }
            
            // 使用scan命令替代keys命令，避免阻塞Redis
            try (Cursor<byte[]> cursor = Objects.requireNonNull(redisTemplate.getConnectionFactory())
                    .getConnection().scan(ScanOptions.scanOptions().match(pattern).count(1000).build())) {
                
                Set<String> results = new HashSet<>();
                while (cursor.hasNext()) {
                    byte[] keyBytes = cursor.next();
                    String key = new String(keyBytes);
                    results.add(key);
                }
                return results;
            }
        } catch (Exception e) {
            log.error("RedisUtil.keys pattern:{}", pattern, e);
            return Collections.emptySet();
        }
    }

    /**
     * 写入集合 - 可变数组
     *
     * @param key        键
     * @param values     值
     * @param expireTime 过期时间（单位：秒），-1 代表永不过期
     * @return boolean
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public boolean setList(final String key, Collection<Object> values, Integer expireTime) {
        try {
            redisTemplate.opsForList().rightPushAll(key, values);
            isExpire(key, expireTime);
            return true;
        } catch (Exception e) {
            log.error("RedisUtil.setList key:{}, values:{}, expireTime:{}", key, values, expireTime, e);
            return false;
        }
    }

    /**
     * 从列表中删除 - 某一个值
     *
     * @param key   钥匙
     * @param value 价值
     * @date 2024/04/05
     * @author SongRenShuo
     */
    public void removeFromList(final String key, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, 0, value);
            if (remove==null|| remove ==0L){
               log.error("RedisUtil.removeFromList 删除失败 key:{}, value:{}", key, value);
               log.error("RedisUtil.removeFromList 删除失败 key:{}, value:{}", key,toJsonStr(value) );
           }
        } catch (Exception e) {
            log.error("RedisUtil.removeFromList key:{}, value:{}", key, value, e);
        }
    }

    /**
     * 将键值对 (hk, hv) 以键 key 的形式存储到 Redis 的哈希表中，并可设置过期时间。
     *
     * @param key        Redis 中哈希表的键
     * @param hk         哈希表中的键
     * @param hv         哈希表中键 hk 对应的值
     * @param expireTime 过期时间（单位：秒），-1 代表永不过期
     * @return 操作成功返回 true，失败返回 false
     * @date 2024/05/18
     * @author SongRenShuo
     */
    public boolean setHashAsKV(String key, Object hk, Object hv, Integer expireTime) {
        try {
            redisTemplate.opsForHash().put(key, hk, hv);
            isExpire(key, expireTime);
            return true;
        } catch (Exception e) {
            log.error("RedisUtil.setHashAsKV key:{}, hk:{}, hv:{}, expireTime:{}", key, hk, hv, expireTime, e);
            return false;
        }
    }

    /**
     * 将 Map 数据设置到 Redis 的哈希表中。
     *
     * @param key        Redis 中哈希表的键
     * @param map        要存储在哈希表中的 Map 数据，其中key-value对代表哈希表中的字段和值
     * @param expireTime 过期时间（单位：秒），-1 代表永不过期
     * @return 如果操作成功返回 true，如果操作失败返回 false
     * @date 2024/05/18
     * @author SongRenShuo
     */
    public boolean setHashAsMap(String key, Map<?, ?> map, Integer expireTime) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            isExpire(key, expireTime);
            return true;
        } catch (Exception e) {
            log.error("RedisUtil.setHashAsMap key:{}, map:{}, expireTime:{}", key, map, expireTime, e);
            return false;
        }
    }

    /**
     * 获取哈希表
     *
     * @param key 键
     * @return {@link Map }<{@link K }, {@link V }>
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public <K, V> Map<K, V> getMap(String key) {
        Map<K, V> map = (Map<K, V>) redisTemplate.opsForHash().entries(key);
        return CollectionUtils.isEmpty(map) ? Collections.emptyMap() : map;
    }

    /**
     * 获取哈希对象
     *
     * @param k1 K1
     * @param k2 K2
     * @return {@link Object}
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public Object getHashObject(String k1, String k2) {
        return redisTemplate.opsForHash().get(k1, k2);
    }

    /**
     * 判断是否存在 key
     *
     * @param key key
     * @return boolean
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public boolean hasKey(final String key) {
        try {
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        } catch (Exception e) {
            log.error("RedisUtil.hasKey key:{}", key, e);
            return false;
        }
    }

    /**
     * 删除 key
     *
     * @param key key
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public void del(final String key) {
        if (hasKey(key)) {
            log.info("RedisUtil.del key:{}",key);
            redisTemplate.delete(key);
        }
    }

    /**
     * 批量删除key
     *
     * @param keys keys
     * @return boolean
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public void delKeys(List<String> keys) {
        keys.forEach(this::del);
    }
    /**
     * 批量删除key
     *
     * @param keys keys
     * @return boolean
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public void delKeys(String... keys) {
        Arrays.stream(keys).forEach(this::del);
    }
    /**
     * 设置分布式锁
     *
     * @param value      值
     * @param expireTime 过期时间（单位：秒），建议不要使用-1避免死锁
     * @return boolean
     * @date 2024/01/09
     * @author SongRenShuo
     */
    public Boolean setIfAbsent(final String key, Object value, Integer expireTime) {
        Boolean ifAbsent;
        try {
            ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
            // 使用 SETNX 判断是否存在 保证只执行一次并设置20秒过期
            ifAbsent = valueOps.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("RedisUtil.setIfAbsent key:{},value:{},expireTime:{}", key, value, expireTime, e);
            return false;
        }
        return ifAbsent != null && ifAbsent;
    }

    /**
     * 设置过期时间
     *
     * @param key        键
     * @param expireTime 过期时间（单位：秒），-1 代表永不过期
     */
    private void isExpire(String key, Integer expireTime) {
        // 判断并设置过期时间
        if (expireTime != -1) {
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
        } else {
            // 如果expireTime为-1，则不设置过期时间，实现永不过期
            redisTemplate.persist(key);
        }
    }
}
